2016-11-17 更新:如果已经了解运行时权限的基本使用,可以直接参考这篇文章:Android M 权限最佳实践

样例代码:https://github.com/kinneyyan/RuntimePermissionSample

1. 新的权限机制

6.0 开始将权限分为两类。

一类是 Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等。此类权限在应用安装时就被授予(同 6.0 之前)。

另一类是 Dangerous Permission,一般是涉及到用户隐私的,在 app 使用时需要用户进行授权,比如读取 sdcard、访问通讯录等。此类权限是分组的:

image

同一组的任何一个权限被授权了,其他权限也自动被授权。

2016-03-11 更新:所有需要申请的权限还是必须在 AndroidManifest 中声明,否则,即使同组一个权限被授权了,一个未在 AndroidManifest 中声明的权限授权还是会失败。

2. 关于 targetSdkVersion 需要注意的

  • 若 targetSdkVersion 低于 23,将使用旧有规则:用户在安装的时候不得不接受所有权限,安装后 app 就有了那些权限。不过用户依然可以在设置中取消已经同意的授权。
  • 若 targetSdkVersion 高于 23,如果 app 在使用一些敏感权限的时候没有做运行时权限的代码处理,app 会直接 crash。

3. 相关 API 以及使用步骤

假设我们的 app 有添加联系人的功能:

  1. 在 AndroidManifest 文件中添加需要的权限 android.permission.WRITE_CONTACTS
  2. 在添加联系人的代码之前,检查权限
1
2
3
4
5
6
7
8
int hasWriteContactsPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS);
if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
//申请授权
...
return;
}
//添加联系人
insertDummyContact();

ContextCompat.checkSelfPermission:用于检测某个权限是否已经被授予,返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED

  1. 申请授权
1
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_WRITE_CONTACTS);

ActivityCompat.requestPermissions:第一个参数是 Context;第二个参数是需要申请的权限的字符串数组,很明显这里可以一次申请多个;第三个参数为 requestCode,主要用于回调的时候检测。

  1. 处理申请回调。不论用户同意还是拒绝,activity 的 onRequestPermissionsResult 都会被回调来通知结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_WRITE_CONTACTS:
// Permission Granted
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
insertDummyContact();
}
// Permission Denied
else {
Toast.makeText(MainActivity.this, "WRITE_CONTACTS permission denied", Toast.LENGTH_SHORT).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults):第一个参数 requestCode 不用说;第二个参数为申请的权限,例如 Manifest.permission.WRITE_CONTACTS;第三个参数为申请结果。如果此次回调是一次申请多个权限的情况,那第二个参数和第三个参数为对应关系。

至此权限申请的步骤走通,不过还有个 API 需要提下:

ActivityCompat.shouldShowRequestPermissionRationale(Activity activity, String permission):该方法只有在用户在上一次已经拒绝过你的这个权限申请时返回 true;其余情况例如用户勾选了”不再显示”时均返回 false。这个 API 的目的主要用于给用户一个申请权限的解释,我们可以弹个对话框告知用户为什么需要这个权限,点击确定时再去申请权限。加入此方法的申请权限代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int hasWriteContactsPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS);
if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
//该方法只有在用户在上一次已经拒绝过你的这个权限申请返回true;勾选了"不再显示"时返回false
//你需要给用户一个解释,为什么要授权
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_CONTACTS)) {
showConfirmDialog("please accept WRITE_CONTACTS permission request.", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_WRITE_CONTACTS);}});
return;
}
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_WRITE_CONTACTS);
}
insertDummyContact();

如果用户勾选了“不再显示”拒绝后,再次申请权限时,在 onRequestPermissionsResult 回调方法中走权限拒绝的方法,如果用户又想开启此权限的话,我们可以通过 shouldShowRequestPermissionRationale 返回值判断是否勾选“不再显示”,是的话在回调方法判断权限拒绝的代码块中弹一个对话框告知用户:在设置-应用-权限管理中去开启。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
case REQUEST_CODE_WRITE_CONTACTS:
// Permission Granted
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
insertDummyContact();
}
// Permission Denied
else {
//若用户在拒绝权限时勾选了"不再显示",显示对话框提示用户
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_CONTACTS)) {
showConfirmDialog("WRITE_CONTACTS permission denied, please enable it in Settings-Apps.", null);
return;
}
Toast.makeText(MainActivity.this, "WRITE_CONTACTS permission denied", Toast.LENGTH_SHORT).show();
}
break;

4. 第三方开源库

前两个使用了注解


参考文章
Android M 新的运行时权限开发者需要知道的一切 > Android 6.0 运行时权限处理